import urllib
import string
import time

from activist.DataStore import DataStore
import activist.Person
import activist.Group
import activist.Event
from activist.MetaData import MetaData
import activist.Time
from activist.CommandProcessor import CommandProcessor
from activist.web.page import StaticPage
from activist.web.page import Error404, YearTooBig
import activist.errors
from activist import iactivist

from twisted.web import resource, server, static, util
from twisted.python import log
from twisted.python import failure
from twisted.protocols import http
import activist.printing.utils

from zope import interface

import nevow.loaders
from nevow import flat
from nevow import compy
import nevow

from formless import webform
from formless import annotate

CLASS_TO_URL = {
    activist.Person.Person: 'person',
    activist.Group.Group: 'group',
    activist.Event.Event : 'event'
    }

from activist.web.view import *
from activist.web.page import *


class MediaResource:
    interface.implements(inevow.IResource)
    id = None
    
    def locateChild(self, ctx, segments):
        if len(segments) == 1:
            self.id = segments[0]
        return self, ()
    
    def renderHTTP(self,ctx):
        request = ctx.locate(inevow.IRequest)
        if self.id is not None:
            data = DataStore[self.id]
            request.setHeader('content-type',data.mimetype())
            return data.data()
        return 'no ID'


class Wait(resource.Resource):
    page = WaitPage()
    
    def render(self,request):
        rtn = self.page.renderHTTP(request)
        rtn.addCallback( cb_done, request )
        return server.NOT_DONE_YET


def sortListingsByDate(a,b):
    return -cmp(a.info().whenRaw(),b.info().whenRaw())
def sortListingsByPlace(a,b):
    return cmp(a.info().where(),b.info().where())
def sortListingsByTitle(a,b):
    return cmp(a.info().title(),b.info().title())



class ExceptionHandler:
    interface.implements(inevow.ICanHandleException)

    def __init__(self,ctx):
        pass
    
    def renderHTTP_exception(self, ctx, reason):
        request = inevow.IRequest(ctx)

        if reason.type == activist.errors.SecurityViolation:
            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
            request.write("<html><head><title>Security Violation</title></head><body>")
            request.write("<h1>Security Violation</h1><pre>")
            request.write(str(reason.value))
            request.write("</pre></body></html>")
            server.Request.finish(request)
            return
            
        request.setResponseCode(http.INTERNAL_SERVER_ERROR)
        request.write("<html><head><title>Exception</title></head><body>")
        from nevow import failure
        result = failure.formatFailure(reason)
        request.write(''.join(flat.flatten(result)))
        request.write("</body></html>")
        server.Request.finish(request)
        
    def renderInlineException(self,request,reason):
        return '[[EXCEPTION]]'
#compy.registerAdapter(ExceptionHandler, nevow.context.RequestContext, nevow.inevow.ICanHandleException)



class ResourceGeneric(resource.Resource):
    interface.implements(inevow.IResource)

    
    isLeaf = True
    requireLogin = False
    page = None
    cache = True

    def makePage(self,request):
        return GenericPage()

    def checkObject(self,obj):
        return True

    def preparseArgs(self,request):
        """override this if you want to look through the GET/POST args
        before the page is rendered in the normal fashion"""

        mind = request.session.mind

        mind._optionsFromArgs(request.args)
        
        if request.args.has_key('delete'):
            id = request.args['listingid'][0]
            print "DELETE!",id
            del request.args['delete']
            del request.args['listingid']
            try:
                edit = DataStore[id]
                if request.session.mind.canEdit(edit):
                    edit.delete(request.session.mind.person())
                    print "   deleted!"
                else:
                    print "   can't do that!"
            except KeyError:
                pass
            
        if request.args.has_key('approve'):
            id = request.args['listingid'][0]
            data = DataStore[id]
            if mind.canModerate(data):
                if hasattr(data,'info'):
                    data.info().approve(mind.person())
                else:
                    data.approve(mind.person())
            del request.args['approve']
            del request.args['listingid']
        elif request.args.has_key('unapprove'):
            id = request.args['listingid'][0]
            data = DataStore[id]
            if mind.canModerate(data):
                if hasattr(data,'info'):
                    data.info().unapprove(mind.person())
                else:
                    data.unapprove(mind.person())
            del request.args['unapprove']
            del request.args['listingid']


    def locateChild(self, ctx, segments):
        request = ctx.locate(inevow.IRequest)

        print "LOCATE",segments,"login is",self.requireLogin,"for",self
        print "   args:",request.args
        if self.requireLogin:
            if not request.session.mind or request.session.mind.isAnonymous():
                request.args.clear()
                request.args['redirect'] = ["/".join(segments)]
                request.args['required'] = [1]
                return util.Redirect('/login'), ()
                return util.Redirect('/login?redirect=%s&required=1'%"/".join(segments))

        rtn = self.preparseArgs(request)
        if rtn:
            return rtn
     
        if not self.page or self.cache == False:
            self.page = self.makePage(request)
        page = self.page
        try:
            object = DataStore[segments[-1]]
        except KeyError:
            object = None

        if object is None or not self.checkObject(object):
            return util.Redirect('/'), ()
        
        inevow.IRequest(ctx).session.mind.object = object
        return page, ()

    def renderHTTP(self,ctx):
        request = inevow.IRequest(ctx)

        if self.requireLogin:
            if not request.session.mind or request.session.mind.isAnonymous():
                request.args.clear()
                return util.redirectTo('/login?redirect=%s&required=1'%ctx.path,request)
            
        if not self.page or self.cache == False:
            self.page = self.makePage(request)

        rtn = self.preparseArgs(request)

        if rtn:
            return rtn.render(request)
        
                                        # WHAT THE FUCK?!
        self.renderer = self.page.renderer
        return self.page.renderHTTP(ctx)
            

class Object(ResourceGeneric):
    cache = False

    def makePage(self,request):
        class Foo(GenericPage):
            def data_type(self,ctx,data):
                return 'no type'
            def data_id(self,ctx,data):
                return 'no id'
            def render_body(self,ctx,data):
                lst = []
                for x in DataStore.keys():
                    lst.append(tags.li[tags.a(href='/object/%s'%x)[str(DataStore[x].__class__).split('.')[-1][:-2]]])
                return ctx.tag[tags.ul[lst]]


        if len(request.postpath) == 0:
            return Foo()
        return GenericPage()

class Article(ResourceGeneric):
    isLeaf = True
    def makePage(self,request):
        from activist.web.page import ArticlePage
        return ArticlePage()

class Admin(ResourceGeneric):
    isLeaf = True
    requireLogin = True
    
    def preparseArgs(self,ctx):
        ResourceGeneric.preparseArgs(self,ctx)
        request = inevow.IRequest(ctx)

        if request.args.has_key("admin_id"):
            values = request.args["admin_id"]
            del request.args["admin_id"]
            for id in values:
                data = DataStore[id]
                
                if request.args.has_key("admin_%s"%id):
                    if request.args["admin_%s"%id][0] == 'on':
                        if not data.isAdministrator():
                            data.make_administrator(request.session.mind.person(),True)
                    del request.args["admin_%s"%id]
                else:
                    if data.isAdministrator():
                        data.make_administrator(request.session.mind.person(),False)
                
                if request.args.has_key("moderator_%s"%id):
                    if request.args["moderator_%s"%id][0] == 'on':
                        if not data.isModerator():
                            data.make_moderator(request.session.mind.person(),True)
                    del request.args["moderator_%s"%id]
                else:
                    if data.isModerator():
                        data.make_moderator(request.session.mind.person(),False)
                
                if request.args.has_key("commentator_%s"%id):
                    if request.args["commentator_%s"%id][0] == 'on':
                        if not data.isCommentator():
                            data.make_commentator(request.session.mind.person(),True)
                    del request.args["commentator_%s"%id]
                else:
                    if data.isCommentator():
                        data.make_commentator(request.session.mind.person(),False)

    def locateChild(self,ctx,segments):
        if segments == ('messages',):
            return AdminMessages(), ()
        return None, None

    def makePage(self,request):
        from activist.web.page import AdminPage
        return AdminPage()

class CreateMedia(ResourceGeneric):
    requireLogin = True
    def makePage(self,request):
        return CreateMediaPage()


class PrintEvent(ResourceGeneric):
    isLeaf = True

    def renderHTTP(self,ctx):
        request = inevow.IRequest(ctx)
        d = activist.printing.utils.getPDFDataForEventList( self.filtered )
        def _cbWritePdf(data, request):
            request.setHeader( 'content-type', 'application/pdf' )
            request.write(data)
        d.addCallback( _cbWritePdf, request )
        return d
    
    def locateChild(self, ctx, segments):
        """FIXME TODO move this to the page class...?

        ALSO: duplicated code; this is same/similar to Event.locatechild...

        """
        request = ctx.locate(inevow.IRequest)
        
        if len(segments) > 1 and segments[0] == 'event' and segments[1] in ['time','place','category']:
            option = segments[1]
            year = None
            month = None
            if len(segments)>2:
                extra = segments[2:]
                try:
                    year = int(extra[0])
                    month = int(extra[1])
                except (IndexError,ValueError):
                    t = time.localtime()
                    year = t.tm_year
                    month = t.tm_mon
            else:
                t = time.localtime()
                year = t.tm_year
                month = t.tm_mon
            objects = []
            viewunmoderated = 'unmoderated' in segments
            thisyear = time.localtime().tm_year
            if year - thisyear > 1: return YearTooBig(),()

            object = []
            for x in DataStore.getByClass(activist.Event.Event):
                objects.append(x)
                
            if option == 'time':
                objects.sort(sortListingsByDate)
            elif option == 'place':
                objects.sort(sortListingsByPlace)
            elif option == 'category':
                objects.sort(sortListingsByTitle)

            if option == 'time' and year and month:
                try:
                    class Filter:
                        def __init__(self,y,m):
                            self.year = y
                            self.month = m
                        def __call__(self,event):
                            tm = event.info().whenRaw()
                            return tm.isValid(self.year,self.month,None)
                    objects = filter(Filter(year,month),objects)
                except (IndexError,ValueError):
                    pass

            request = inevow.IRequest(ctx)
            if viewunmoderated:
                self.filtered = objects
            else:
                self.filtered = filter( lambda x: x.isApproved(), objects )
            return self,()

        return Error404(),()

class Event(ResourceGeneric):
    isLeaf = True
    listingOptionsPage = GenericPage(docFactory=loaders.htmlfile(Config.template('static/event-listing-options.html')))
    
    def checkObject(self,obj):
        return isinstance(obj,activist.Event.Event)
    
    def makePage(self,request):
        rtn = EventPage()
        if request.args.has_key('edit'):
            rtn.edit = str(request.args['edit'][0])
        return rtn

    def preparseArgs(self,request):
        ResourceGeneric.preparseArgs(self,request)
        
        mind = request.session.mind
        # FIXME: should put some of the paranoid logic into ResourceGeneric
        if 0:
            if request.args.has_key('approve'):
                id = request.args['listingid'][0]
                d = DataStore[id]
                if mind.canModerate(d):
                    ## this won't work after I changed event.approve() to not proxy to eventinformation
                    ## (i.e. need to do d.info().approve if d.info exists
                    d.approve(mind.person())
                else:
                    raise failure.Failure(activist.errors.SecurityViolation("attempt to moderate un-moderatable thing; 1"))
                
            elif request.args.has_key('unapprove'):
                id = request.args['listingid'][0]
                d = DataStore[id]
                if mind.canModerate(d):
                    if d.hasattr('info'):
                        d.info().unapprove(mind.person())
                    else:
                        d.unapprove(mind.person())
                else:
                    raise activist.errors.SecurityViolation("attempt to moderate un-moderatable thing; 2")
                
            elif request.args.has_key('delete'):
                print "GHETTO DELETE"
                id = request.args['listingid'][0]
                if DataStore.has_key(id):
                    d = DataStore[id]
                    if mind.canModerate(d):
                        if d.isApproved():
                            if d.hasattr('info'):
                                d.info().unapprove(mind.person())
                            else:
                                d.unapprove(mind.person())

                        d.delete(mind.person())
                    else:
                        raise activist.errors.SecurityViolation("attempt to moderate un-moderatable thing!")
                            
        if request.args.has_key('comment'):
            showcomment = request.args['comment'][0]
            request.session.mind.addShowComment(showcomment)
        elif request.args.has_key('nocomment'):
            showcomment = request.args['nocomment'][0]
            request.session.mind.removeShowComment(showcomment)

    def locateChild(self, ctx, segments):
        """FIXME TODO move this to the page class...?

        better handling of "non-existant" dates, like far-future (so google doesn't fuck us by indexing invite
        resources)
        """
        request = inevow.IRequest(ctx)
        #print "EVENT.locatechild:",ctx,segments,request.args
        page = self.makePage(request)
        self.preparseArgs(request)
        
        if len(segments) > 0 and segments[0] in ['time']:#,'place','category']:
            p = EventListPage()
            p.option = segments[0]
            p.year = None
            p.month = None
            t = time.localtime()
            
            if len(segments)>1:
                p.extra = segments[1:]
                p.year = None
                p.month = None
                try:
                    p.year = int(p.extra[0])
                    p.month = int(p.extra[1])
                except (IndexError,ValueError):
                    if p.year != None:
                        return p, ()
                    return util.Redirect('/event/time/%04d/%02d'%(t.tm_year,t.tm_mon)), ()

            else:
                t = time.localtime()
                return util.Redirect('/event/time/%04d/%02d'%(t.tm_year,t.tm_mon)), ()

            if p.year - t.tm_year > 1: return YearTooBig(),()
            
            if 'unmoderated' in segments:
                request.viewunmoderated = True

            request.session.mind.object = []
            for x in DataStore.getByClass(activist.Event.Event):
                request.session.mind.object.append(x)
                
            if p.option == 'time':
                request.session.mind.object.sort(sortListingsByDate)
            elif p.option == 'place':
                request.session.mind.object.sort(sortListingsByPlace)
            elif p.option == 'category':
                request.session.mind.object.sort(sortListingsByTitle)

            if p.option == 'time' and p.year and p.month:
                if 0:
                    try:
                        year = p.year
                        month = p.month
                        class Filter:
                            def __init__(self,y,m):
                                self.year = y
                                self.month = m
                            def __call__(self,event):
                                tm = event.info().whenRaw()
                                return tm.isYear(self.year) and tm.isMonth(self.month)
                        request.session.mind.object = filter(Filter(year,month),request.session.mind.object)
                    except (IndexError,ValueError):
                        pass

                request.session.mind.object = DataStore.getEventsForMonth(p.year,p.month)


            if 'print' in segments:
                filtered = filter( lambda x: x.isApproved(), request.session.mind.object )
                data = activist.printing.utils.getPDFDataForEventList( filtered )
                request.setHeader( 'content-type', 'application/pdf' )
                request.write(data)
                request.finish()
                return server.NOT_DONE_YET
                
            return p, ()
        
        if len(segments) and segments[-1] == 'create':
            return util.Redirect('/create/event'), ()

        try:
            data = DataStore[segments[-1]]
            if not self.checkObject(data):
                return util.Redirect('/event/time'), ()
        except KeyError:
            return Error404(), ()

        request.session.mind.object = data
        return page, ()
    

class CreateGroup(ResourceGeneric):
    requireLogin = True
    
    def makePage(self,request):
        return CreateGroupPage()
    
    def preparseArgs(self,ctx):
        """FIXME: move to page.py??"""
        ResourceGeneric.preparseArgs(self,ctx)
        
        request = inevow.IRequest(ctx)

        if request.session.mind[CreateGroupPage].creating == 2:
            request.redirect('/create/event')
            return
            
        if request.args.has_key("preview"):
            request.session.mind[CreateGroupPage].preview = 1
        
        if request.args.has_key('title'):
            request.session.mind[CreateGroupPage].title = request.args['title'][0]
        if request.args.has_key('url'):
            request.session.mind[CreateGroupPage].url = request.args['url'][0]
        if request.args.has_key('text'):
            request.session.mind[CreateGroupPage].text = request.args['text'][0]
        if request.args.has_key('brief'):
            request.session.mind[CreateGroupPage].brief = request.args['brief'][0]
            
        if request.args.has_key('create'):
            if request.session.mind[CreateGroupPage].creating < 1:
                request.session.mind[CreateGroupPage].creating = 1
            else:
                creator = request.session.mind.person()
                title = request.session.mind[CreateGroupPage].title
                text = request.session.mind[CreateGroupPage].text
                brief = request.session.mind[CreateGroupPage].brief
                url = request.session.mind[CreateGroupPage].url

                if request.session.mind[CreateGroupPage].editing is None:
                    group = activist.Group.Group()
                    group.create(title,brief,text,url,[creator],[],[creator],None,None)
                else:
                    group = request.session.mind[CreateGroupPage].editing
                    group.edit(title,brief,text,url,creator)
                
                request.session.mind[CreateGroupPage].creating = 2
                request.session.mind[CreateGroupPage].lastid = group.id()
        else:
            request.session.mind[CreateGroupPage].creating = 0
    
    
class Group(ResourceGeneric):
    isLeaf = True
    def makePage(self,request):
        return GroupPage()

class Person(ResourceGeneric):
    def makePage(self,request):
        return PersonPage()

class Comment(ResourceGeneric):
    def makePage(self,request):
        return CommentPage()

class CreateComment(ResourceGeneric):
    isLeaf = True

    def makePage(self,request):
        return CreateCommentPage()


class Login(ResourceGeneric):
    def makePage(self,request):
        return LoginPage();

class Logout:
    isLeaf = True
    interface.implements(inevow.IResource)
    
    def renderHTTP(self, ctx):
        request = ctx.locate(inevow.IRequest)
        redir = '/'
        if request.args.has_key('redirect'):
            redir = request.args['redirect'][0]
            
        log.msg( 'Logging out due to user reqeust.' )
        request.session.expire()
        return util.redirectTo(redir,request)


class Root:
    interface.implements(inevow.IResource)

    children = {}
    def __init__(self):
        mapdata = open(Config.template('MAP'),'r').readlines()
        for line in mapdata:
            line = line.strip()
            if not len(line) or line[0] == '#':
                continue
            tup = line.split(' ')
            path = tup[0]
            klass = tup[1]
            if len(tup) > 2:
                cmd = 'rez = activist.web.resource.%s(' % klass
                x = tup[2]
                if klass == 'StaticPage':
                    cmd = 'rez = activist.web.resource.StaticPage("%s", ' % Config.template(x)
                    continue
                else:
                    cmd = cmd + 'docFactory=loaders.htmlfile("' + Config.template(x) + '"), '
                if len(tup) > 3 and cmd:
                    cmd = cmd + 'overrideType="' + tup[3] + '", '
                if cmd: cmd = cmd[:-2]+')'
                
            else:
                cmd = 'rez = activist.web.resource.%s()' % klass
                
            exec(cmd)

            if len(tup) > 3:
                rez.mimetype = tup[-1]
            self.putChild(path,rez)
            if 0:
                segments = path.split('/')
                child = self
                while len(segments):
                    if len(segments) == 1:
                        child.putChild( segments[0], rez )

                    else:
                        child = child.children[segments[0]]
                    segments = segments[1:]
                
    def locateChild(self,context,origpath):
        #print "locatechild:",context,origpath
        origpath = tuple(iactivist.IActivistPath(context).activistpath().split('/')[1:])
        if origpath == ('',):
            origpath = ('index.html',)
        origpath = filter(lambda x: x != '', origpath)
        path = origpath
        extra = ()
        while len(path):
            flat = '/'.join(path)
            if self.children.has_key(flat+'/') and len(extra) > 0:
                return self.children[flat+'/'], extra
            if self.children.has_key(flat):
                return self.children[flat], extra
            else:
                extra = (path[-1],) + extra
                path = path[:-1]

        key = '/' + '/'.join(origpath)
        if not self.children.has_key(key):
            return Error404(),()
        return self.children[key], ()

    def renderHTTP(self,ctx):
        raise "not implemented"

    def putChild(self,segments,child):
        self.children[segments] = child

    def logout(self):
        print "LOGOUT"

def cb_done(result,ctx):
    request = ctx.locate(inevow.IRequest)
    request.setHeader('Cache-Control','no-cache')
    if hasattr(request,'start_render'):
        print "render time:",(time.time()-request.start_render),request.path
    request.finish()


